using System;
using System.CodeDom.Compiler;
using System.Collections.Generic;
using System.Diagnostics;
using System.Drawing;
using System.Drawing.Imaging;
using System.IO;
using System.Linq;
using NAnt.Core;

namespace LeanUi.ImageBundle
{
    public class ImageBundleGenerator {

        const string BASENAME = "ImageBundle"; 
        private static readonly string[] imageTypes = new[] { ".jpg", ".gif", ".png" };
        private static readonly string[] ignoreDirectories = new[] { ".svn" };

        private readonly Action<Level, string> _loggingFunc;
        private readonly CssBuilder _cssbuilder = new CssBuilder();
        private readonly ImageProxyGenerator _proxyGenerator = new ImageProxyGenerator();
        private readonly ImageMerger _imageMerger = new ImageMerger( new VariedSizeArrangingStrategy() );
       // private readonly DataUriBuilder _dataUriBuilder = new DataUriBuilder();


        private string BaseNameSpace { get; set; }
        private DirectoryInfo BaseDir { get; set; }

        public DirectoryInfo OutputProxyDir { get; set; }
        public DirectoryInfo OutputAssembly { get; set; }
        public DirectoryInfo OutputImageDir { get; set; }
        public DirectoryInfo OutputCssDir { get; set; }
        public FileInfo OptiPngPath { get; set; }
        public int MaxSpriteSizeInKByte { get; set; }
        public int MaxPixelCount { get; set; }
        public bool CreateSingleBundle { get; set; }
        public bool UseDisabledRule { get; set; }

        
        public ImageBundleGenerator(
            DirectoryInfo baseDir, 
            DirectoryInfo outputProxyDir,
            DirectoryInfo outputAssembly,
            DirectoryInfo outputCssDir,
            DirectoryInfo outputImageDir, 
            string baseNameSpace,
            bool useDisabledRule,
            Action<Level,string> loggingFunc)
        {
            _loggingFunc = loggingFunc;
            if (baseDir == null) throw new ArgumentNullException("baseDir");
            if (outputProxyDir == null && outputAssembly == null) throw new ArgumentNullException("outputProxyDir/outputAssembly");
            if (outputCssDir == null) throw new ArgumentNullException("outputCssDir");
            if (outputImageDir == null) throw new ArgumentNullException("outputImageDir");
            if (baseNameSpace == null) throw new ArgumentNullException("baseNameSpace");

            BaseNameSpace = baseNameSpace;
            BaseDir = baseDir;

            OutputProxyDir = outputProxyDir;
            OutputAssembly = outputAssembly;
            OutputCssDir = outputCssDir;
            OutputImageDir = outputImageDir;

            UseDisabledRule = useDisabledRule;
            MaxSpriteSizeInKByte = (int.MaxValue/1024)-1;
        }

        public void Log(Level level, string message)
        {
            _loggingFunc(level, message);
        }

        public void Log(Level level, string messageFormat, params string[] values)
        {
            _loggingFunc(level, string.Format(messageFormat, values));
        }


        public void Generate()
        {
            CreateDir(OutputProxyDir);
            CreateDir(OutputCssDir);
            CreateDir(OutputImageDir);

            DirectoryInfo[] subdirs = GetAllSubDirectories(BaseDir).ToArray();

            if (CreateSingleBundle)
            {  
                List<ImageRect> imageRects = GetImageRectsForDir(subdirs);
                if (imageRects.Any())
                {
                    GenerateBundle(imageRects, BaseDir, BASENAME);
                }
            }
            else
                foreach(var dir in subdirs)
                {
                    List<ImageRect> imageRects = GetImageRectsForDir(dir);
                    if (imageRects.Any())
                    {
                        GenerateBundle(imageRects, BaseDir, NamingHelper.GetBundleNameFor(dir.Name)+"ImageBundle");
                    }
                }
        }


        private void CreateDir(DirectoryInfo dir)
        {
            if (dir != null && !dir.Exists) {
                dir.Create();
            }
        }

        public static IEnumerable<DirectoryInfo> GetAllSubDirectories(DirectoryInfo dirInfo)
        {
            var dirs = new[] { dirInfo };
            return dirs.Concat( dirInfo.GetDirectories()
                             .Where( dir => !ignoreDirectories.Contains( dir.Name ) )
                             .SelectMany( GetAllSubDirectories ));
        }
            

        private void GenerateBundle(IList<ImageRect> imageRects, DirectoryInfo basedir, string basename)
        {
          //  var nonbackgrounds = imageRects.Where(x=> x.BackgroundType == BackgroundType.None).ToArray();
         //   var backgrounds = imageRects.Where(x => x.BackgroundType != BackgroundType.None).ToArray();


            Bitmap bundledImage = _imageMerger.DrawBundledImage(imageRects);
           // String bgcssrules = _dataUriBuilder.GenerateCss(backgrounds);
            String cssrules = _cssbuilder.GenerateCss(imageRects);
            String proxycode = _proxyGenerator.GenerateImageBundleSource(imageRects, basedir, BaseNameSpace, cssrules);

            GenerateCssFile(basename, /*bgcssrules+Environment.NewLine+*/cssrules);
            GeneratePngImage( basename, bundledImage);
            GenerateProxy( basename, proxycode);
        }

        private List<ImageRect> GetImageRectsForDir(params DirectoryInfo[] dirs)
        {
            var imageNameToImageRectMap = new Dictionary<string, ImageRect>();

            foreach(var dir in dirs) 
            {
                FileInfo[] files = dir.GetFiles();
                var smallImages = files.Where(f=> IsSmallImage(f) && !IsAnimatedGif(f));

                foreach (var file in smallImages)
                {
                    var spriteName = NamingHelper.GetNameFor(file.Name);
                    if (!imageNameToImageRectMap.ContainsKey(spriteName))
                    {
                        var rect = new ImageRect( file );
                        imageNameToImageRectMap[spriteName] = rect;
                    }
                }
            }
            return imageNameToImageRectMap.Values.ToList();
        }

        private bool IsSmallImage(FileInfo f)
        {

            var res = imageTypes.Contains(f.Extension.ToLower()) &&
                      !f.Name.Contains("ImageBundle.") &&
                      f.Name != "null.gif" &&
                      f.Length < MaxSpriteSizeInKByte*1024;
            if (!res) return false;

            var size = Image.FromFile(f.FullName).Size;
            int height = size.Height;
            int width = size.Width;
            return (width*height) < MaxPixelCount;
        }


        private bool IsAnimatedGif(FileInfo f)
        {
            return ImageAnimator.CanAnimate(Image.FromFile(f.FullName));
        }

        private void GenerateProxy( string baseName, string proxycode )
        {
            if (string.IsNullOrEmpty(proxycode)) {
                return;
            }

            if (OutputProxyDir != null)
            {
                string proxyPath = Path.Combine(OutputProxyDir.FullName, baseName + ".cs");
                File.WriteAllText(proxyPath, proxycode.CorrectNewLines());
            }

            if (OutputAssembly != null)
            {
                string assemblyPath = Path.Combine(OutputAssembly.FullName, baseName + ".dll");
                CompileSources(assemblyPath, proxycode.CorrectNewLines());
            }
        }

        private void CompileSources(string outputPath, params string[] sources)
        {
            var compiler = CodeDomProvider.CreateProvider("CSharp");
            var options = new CompilerParameters();
            options.ReferencedAssemblies.Add("LeanUi.ImageBundleBase.dll");
            options.OutputAssembly = outputPath;
            options.GenerateExecutable = false;

            var res = compiler.CompileAssemblyFromSource(options, sources);
            ShowCompilerErrors(sources, res);
        }

        private void ShowCompilerErrors(string[] sources, CompilerResults res)
        {
            if (res.Errors.Count > 0)
            {
                // Display compilation errors.
                Log(Level.Debug, "Errors building {0} into {1}", sources[0], res.PathToAssembly);
                foreach (CompilerError ce in res.Errors)
                {
                    Log(Level.Debug, "  {0}", ce.ToString());
                    Log(Level.Debug, "");
                }
            }
            else
            {
                Log(Level.Info, "Source {0} built into {1} successfully.", sources[0], res.PathToAssembly);

            }
        }

        private void GenerateJpgImage( string baseName, Bitmap bundledImage )
        {
            string jpegPath = Path.Combine(OutputImageDir.FullName, baseName + ".jpg");
            bundledImage.SaveJpg(jpegPath, 95L);
        }

        private void GeneratePngImage( string baseName, Bitmap bundledImage )
        {
            string imgPath = Path.Combine(OutputImageDir.FullName, baseName + ".png");
            bundledImage.Save(imgPath, ImageFormat.Png);
            OptimizeImage(imgPath);
        }

        private void GenerateCssFile( string baseName, string cssrules)
        {
            string relativePathFromCssToImages = PathHelper.FindRelativePath( OutputCssDir.FullName, OutputImageDir.FullName );
            string relativePathToSprite = string.Format("{0}/{1}.png", relativePathFromCssToImages, baseName);

            String cssfile = string.Format( cssrules, relativePathToSprite );
            
            if (!string.IsNullOrEmpty(cssfile)) {
                string cssPath = Path.Combine(OutputCssDir.FullName, baseName + ".css");
                File.WriteAllText(cssPath, cssfile.CorrectNewLines());
            }
        }

        private void OptimizeImage(string fullpath)
        {
            if (OptiPngPath != null &&
                OptiPngPath.Exists && 
                fullpath != null &&
                File.Exists(fullpath)) 
            {
                Process optipng = new Process();
                optipng.StartInfo.FileName = OptiPngPath.FullName;
                optipng.StartInfo.Arguments = "-keep "+fullpath;
                optipng.StartInfo.UseShellExecute = false;
                optipng.StartInfo.RedirectStandardOutput = true;
                optipng.StartInfo.CreateNoWindow = true;
                optipng.Start();

                var debug = optipng.StandardOutput.ReadToEnd();
                optipng.WaitForExit();
            }
        }
 
    }
}